package org.example.lib;

import lombok.extern.slf4j.Slf4j;
import org.example.annotations.component.Aspect;
import org.example.annotations.component.Service;
import org.example.lib.aop.AopHandler;
import org.example.aspect.TestAspect;
import org.example.lib.aop.IAspect;
import org.example.lib.utils.ClassHelper;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
import java.util.*;

@Slf4j
public class IocContainer {
    public static final IocContainer instance = new IocContainer();
    private boolean isBuilt = false;

    private final Map<Class<?>, Object> classBeans = new HashMap<>();
    private final Map<Class<?>, Map<Class<?>, Object>> InterfaceBeans = new HashMap<>();
    private final Map<Class<?>, List<Class<?>>> classDependencies = new HashMap<>();
    private final Map<Class<?>, Class<?>> interfaceMaps = new HashMap<>();
    private final Map<Class<?>, List<String>> aspectInfo = new HashMap<>();

    private IocContainer() {}

    @SuppressWarnings("unchecked")
    public <T> T getBean(Class<T> clazz) {
        if (classBeans.containsKey(clazz)) {
            return (T) classBeans.get(clazz);
        } else if (InterfaceBeans.containsKey(clazz)) {
            Map<Class<?>, Object> beans = InterfaceBeans.get(clazz);
            if (beans.size() == 1) {
                return (T) beans.values().iterator().next();
            } else {
                throw new RuntimeException("Multiple beans found for interface " + clazz.getName());
            }
        }
        return (T) classBeans.get(clazz);
    }

    @SuppressWarnings("unchecked")
    public <T> T make(Class<T> clazz) {
        if (clazz.getDeclaredConstructors().length > 1){
            throw new RuntimeException("Multiple constructors found for class " + clazz.getName());
        }
        if (!ClassHelper.isInstantiable(clazz)) {
            throw new RuntimeException("Class " + clazz.getName() + " is not instantiable");
        }
        T instance = hasBean(clazz) ? getBean(clazz) : null;

        if (instance != null) {
            return instance;
        }

        try {
            Constructor<?> constructor = clazz.getDeclaredConstructors().length > 0
                                         ? clazz.getDeclaredConstructors()[0]
                                         : clazz.getConstructor();
            List<Object> params = new ArrayList<>();
            List<Class<?>> dependencies = classDependencies.get(clazz);
            dependencies.forEach(dependency -> {
                Object dependencyInstance;
                if (dependency.isInterface()) {
                    Class<?> dependencyInterface = dependency;
                    dependency = interfaceMaps.get(dependency);
                    dependencyInstance = make(dependency);
                    registerBean((Class<? super Object>) dependencyInterface, dependencyInstance);
                }else{
                    dependencyInstance = make(dependency);
                }

                params.add(dependencyInstance);
                registerBean((Class<? super Object>) dependency, dependencyInstance);
            });

            instance = params.isEmpty()
                       ? (T) constructor.newInstance()
                       : (T) constructor.newInstance(params.toArray());
        } catch (Exception e) {
            log.error("Error creating instance of class {}", clazz.getName(), e);
        }

        return instance;
    }

    public boolean hasBean(Class<?> clazz) {
        return classBeans.containsKey(clazz) || InterfaceBeans.containsKey(clazz);
    }

    public <T> void registerBean(Class<T> clazz, T bean) {
        Object instance = aopProxyHandle(bean);

        if (clazz.isInterface()){
            InterfaceBeans.computeIfAbsent(clazz, k -> new HashMap<>()).put(bean.getClass(), instance);
            return;
        }

        classBeans.put(clazz, instance);
    }

    private Object aopProxyHandle(Object bean) {
        if (!Arrays.stream(bean.getClass().getAnnotations()).map(Annotation::annotationType).toList().contains(Service.class)){
            return bean;
        }

        return bean.getClass().getInterfaces()[0].cast(Proxy.newProxyInstance(
                bean.getClass().getClassLoader(),
                bean.getClass().getInterfaces(),
                new AopHandler(bean, new TestAspect())
        ));
    }

    public <T> void registerBean(T bean) {
        classBeans.put(bean.getClass(), bean);
    }

    public void registerInterface(Class<?> clazz, Class<?> implementClass) {
        interfaceMaps.put(clazz, implementClass);
    }

    public void registerDependency(Class<?> clazz, Class<?> dependency) {
        classDependencies.computeIfAbsent(clazz, k -> new ArrayList<>()).add(dependency);
    }

    public void registerDependency(Class<?> clazz, List<Class<?>> dependencies) {
        classDependencies.computeIfAbsent(clazz, k -> new ArrayList<>()).addAll(dependencies);
    }


    @SuppressWarnings("unchecked")
    public void buildBeans() {
        if (isBuilt) {
            return;
        }
        isBuilt = true;

        classDependencies.forEach((clazz, dependencies) -> {
            if (!Arrays.asList(clazz.getInterfaces()).contains(IAspect.class)) {
                return;
            }
            if (!classBeans.containsKey(clazz)){
                Object aspect = make(clazz);
                Aspect aspectAnnotation = clazz.getAnnotation(Aspect.class);
                this.aspectInfo.computeIfAbsent(clazz, k -> new ArrayList<>())
                        .addAll(Arrays.asList(aspectAnnotation.value()));

                registerBean((Class<? super Object>)clazz, aspect);
            }
        });

        classDependencies.forEach((clazz, dependencies) -> {
            if (!classBeans.containsKey(clazz)){
                registerBean((Class<? super Object>)clazz, make(clazz));
            }
        });
    }
}
